package library;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Logger;

import udp.message.UdpMessage;

import dom.BookReservation;
import dom.EducationalSystemEnum;
import dom.StudentAccount;

/**
 * Utility class for the DRM System
 * @author Alex - 2014
 */
public class LibraryUtil {

	private final static Logger logger = Logger.getLogger(LibraryUtil.class.getName()); 
	
	public SimpleDateFormat dateFormatterFull = new SimpleDateFormat("yyyy.MM.dd:HH:mm:ss.SSS");
	public SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy.MM.dd:HH:mm:ss");
	public SimpleDateFormat todayFormatter = new SimpleDateFormat("yyyy.MM.dd");
	//
	public SimpleDateFormat reservationDate = new SimpleDateFormat("dd-MM-yyyy");
	
	/**
	 * Converts a string date to a date object
	 * @param date
	 * @return
	 */
	public Date getDateFromString(String date){
		try{
			
			String tmp = date.replaceAll("\r", "");
			tmp = tmp.replaceAll("\n", "");
			
			return reservationDate.parse(tmp);
		}
		catch (ParseException ex){
			print("Couldn't parse date string '" + date + "'. Cause: " + ex.getMessage());
			return null;
		}
	}
	
	/**
	 * Converts a string date to a date object
	 * @param date
	 * @return
	 */
	public String getStringFromDate(Date date){
		if (date == null){
			print("the date string is null");
			return "";
		}
		return reservationDate.format(date);
	}
	
	/**
	 * Return the date and time as a string
	 * @return
	 */
	public String nowDateString(){
		Date now = new Date();
		String date = dateFormatter.format(now);
		return date;
	}
	
	/**
	 * Return the date and time of today string
	 * @return
	 */
	public String todayDateString(){
		Date today = sanitizeDate(new Date());
		String date = todayFormatter.format(today);
		return date;
	}
	
	/**
	 * Removes the hours minute and seconds from the string
	 * @param date
	 * @return
	 */
	public Date sanitizeDate(Date date){
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		Date sanitizedDate = cal.getTime();
		return sanitizedDate;
	}
	
	/**
	 * Adds a student account to the data structure that holds all student accounts
	 * @param studentAccToAdd
	 * @param list
	 * @return whether the add was successful 
	 * @throws Exception
	 */
	public boolean addStudentAccountToAccountList(StudentAccount studentAccToAdd, HashMap<String,List<StudentAccount>> list) {
		
		if(list == null){
			return false;
		}
		
		if(studentAccToAdd == null){
			print("Student Account is null");
			return false;
		}

		if(studentAccToAdd.getFirstName() == null || studentAccToAdd.getFirstName().length() <= 0){
			print("First name must not be empty");
			return false;
		}
		
		// Get the first character of the first name
		String key = studentAccToAdd.getFirstName().substring(0, 1).toLowerCase();
		
		synchronized(this){
			// The case where it is the first account with that letter
			if(list.get(key) == null){
				List<StudentAccount> newList = new ArrayList<StudentAccount>();
				newList.add(studentAccToAdd);
				list.put(key, newList);
			}
			// Case where there are elements in the list
			else{
				List<StudentAccount> accounts = list.get(key);
				// check if this account is already in the list
				if(!accounts.contains(studentAccToAdd)){
					accounts.add(studentAccToAdd);
				}
				else{
					print("The student account with username \""+studentAccToAdd.getUsername()+"\" already exists");
					logger.fine("The student account with username \""+studentAccToAdd.getUsername()+"\" already exists");
					return false;
				}
			}
		}
		
		//print("Added Student: " + studentAccToAdd.toString());
		return true;
	}
	
	/**
	 * Checks if the student account passed exists in the student account list
	 * @param studentAccountToAdd
	 * @param list
	 * @return
	 */
	public boolean studentExistsInAccountList(StudentAccount studentAccountToAdd, HashMap<String,List<StudentAccount>> list) {
		
		if(list == null){
			return false;
		}
		
		// Throw exception if the student account is null
		if(studentAccountToAdd == null){
			print("Student Account is null");
			//throw new Exception("Student Account is null");
			return false;
		}
		// Throw exception if the first name is null
		if(studentAccountToAdd.getFirstName() == null || studentAccountToAdd.getFirstName().length() <= 0){
			print("First name must not be empty");
			return false;
		}
		
		// Get the key
		String key = getKey(studentAccountToAdd);
		
		// No accounts with that key
		if(list.get(key) == null){
			return false;
		}
		// Case where there are elements in the list
		else{
			List<StudentAccount> accounts = list.get(key);
			// check if this account is already in the list
			if(accounts.contains(studentAccountToAdd)){
				return true;
			}
			else{
				return false;
			}
		}
	}
	
	public void print(String msg){
		print(msg,"INFO");
	}
	
	public void print(String msg, String type){
		if(type.equals("INFO")){
			System.out.println(msg);
		}
		else if(type.equals("DEBUG")){
			System.out.println(msg);
		}
	}
	
	public Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
        ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
        ObjectInputStream objectStream = new ObjectInputStream(byteStream);
        return (UdpMessage) objectStream.readObject();
    }
	
	public String buildNonReturnersString(List<BookReservation> overdueBookReservations){
		
		StringBuilder sb = new StringBuilder();
		
		if(overdueBookReservations == null || overdueBookReservations.isEmpty()){
			//sb.append("There are no overdue books!");
			sb.append("");
			return sb.toString();
		}
		
		// Sort overdue reservations by educational institution
		HashMap<EducationalSystemEnum,List<BookReservation>> sortedReservations = new HashMap<EducationalSystemEnum, List<BookReservation>>();
		for(BookReservation overdueBookReservation : overdueBookReservations){
			if(sortedReservations.get(overdueBookReservation.getStudentAccount().getEdu()) == null){
				sortedReservations.put(overdueBookReservation.getStudentAccount().getEdu(), new ArrayList<BookReservation>());
			}
			sortedReservations.get(overdueBookReservation.getStudentAccount().getEdu()).add(overdueBookReservation);
		}
		
		for(EducationalSystemEnum key : sortedReservations.keySet()){
			List<BookReservation> brs = sortedReservations.get(key);
			sb.append("-----------------------------------------------------------\n");
			sb.append(brs.get(0).getStudentAccount().getEdu().toString() + ": \n");
			sb.append("-----------------------------------------------------------\n");
			for(BookReservation br : brs){
				if(br.getDaysOverdue() > 0){
					sb.append(br.getStudentAccount().getFirstName() + "\t");
					sb.append(br.getStudentAccount().getLastName() + "\t");
					sb.append(br.getStudentAccount().getPhoneNumber() + "\t");
					sb.append(br.getDaysOverdue() + " days overdue\t");
					sb.append(br.getFeesDue() + " $ due");
					sb.append("\n");
				}
			}
			sb.append("-----------------------------------------------------------\n\n");
		}
		return sb.toString();
	}
	
	/**
	 * Returns the key to be used in the hashmap
	 * @param studentAccount
	 * @return
	 */
	public String getKey(StudentAccount studentAccount){
		if(studentAccount.getFirstName() == null || studentAccount.getFirstName().isEmpty()){
			return "";
		}
		return studentAccount.getFirstName().substring(0, 1).toLowerCase();
	}
	
	/**
	 * Returns whether or not the date is after the amount of milliseconds passed
	 * @param date
	 * @param milliseconds
	 * @return
	 */
	public boolean isWithinInterval(Date date, int milliseconds){
		//Calendar cal = Calendar.getInstance();
		//cal.add(Calendar.MILLISECOND, milliseconds);
		//Date adjustedDate = cal.getTime();
		long heartbeartMS = date.getTime();
		Date now = new Date();
		long nowMS = now.getTime();
		heartbeartMS = (heartbeartMS+milliseconds);
		//Date after = new Date(ms+milliseconds);
		//Date adjustedDate = new Date(date.getTime() + milliseconds);
		
		//print("================================");
		//print("DATE NOW        : " + dateFormatterFull.format(now));
		//print("DATE FROM LIST  : " + dateFormatterFull.format(date));
		//print("ADJUSTED DATE   : " + dateFormatterFull.format(after.getTime()));
		//print("================================");
		//print (dateFormatter.format(after.getTime()) + " < " + dateFormatter.format(date) + "?");
		//print (after.getTime() + " < " + now.getTime() + "?");
		//print("================================");
	    //return (after.getTime() < now.getTime());
	    return (heartbeartMS > nowMS);
	}

}
